package fastadmin

import (
	"bytes"
	_ "embed"
	"errors"
	"fmt"
	"gopkg.in/ini.v1"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

const (
	dockerImageName = "shinsenter/php:7.4-fpm-nginx"
)

var (
	//go:embed docker-compose.yml
	dockerComposeFile string
)

type (
	FastAdmin struct {
		dir        string
		env        Env
		readDocker bool
	}
	Env struct {
		DatabaseDsn string
		Debug       bool
		Data        map[string]map[string]string
	}
)

func New(dir string) *FastAdmin {
	return &FastAdmin{
		dir: dir,
		env: Env{},
	}
}
func (admin *FastAdmin) SetEnv(env Env) error {
	cfg := ini.Empty()
	if env.Data == nil {
		env.Data = make(map[string]map[string]string)
	}
	if env.Data["app"] == nil {
		env.Data["app"] = make(map[string]string)
	}

	if env.Debug {
		env.Data["app"]["debug"] = "true"
		env.Data["app"]["trace"] = "true"

	} else {
		env.Data["app"]["debug"] = "false"
		env.Data["app"]["trace"] = "false"
	}
	if env.Data["database"] == nil {
		env.Data["database"] = make(map[string]string)
	}
	if err := parseMySQLDSN(env.DatabaseDsn, env.Data["database"]); err != nil {
		return err
	}
	for key, valMap := range env.Data {
		keyCfg := cfg.Section(key)
		for keyVal, val := range valMap {
			keyCfg.Key(keyVal).SetValue(val)
		}
	}
	return cfg.SaveTo(filepath.Join(admin.dir, ".env"))
}
func (admin *FastAdmin) Run(name, uploadDir string, port uint16) error {
	if !admin.readDocker {
		if err := checkDocker(dockerImageName); err != nil {
			return err
		}
		admin.readDocker = true
	}

	if err := runDocker(name, admin.dir, uploadDir, port); err != nil {
		return err
	}
	return nil
}

type mysqlDSN struct {
	User     string
	Password string
	Host     string
	Port     string
	DBName   string
}

func parseMySQLDSN(dsn string, result map[string]string) error {
	if result == nil {
		return fmt.Errorf("无效的 result")
	}
	// 正则表达式匹配常规格式：user:pass@tcp(host:port)/dbname
	re := regexp.MustCompile(`(?P<username>[^:]+):(?P<password>[^@]+)@tcp\((?P<hostname>[^:]+):(?P<hostport>\d+)\)/(?P<database>[^?]+)`)

	match := re.FindStringSubmatch(dsn)
	if match == nil {
		return fmt.Errorf("无效的 DSN 格式")
	}

	// 结果赋值

	for i, name := range re.SubexpNames() {
		if i == 0 || name == "" {
			continue
		}
		result[name] = match[i]
	}
	return nil
}
func checkDocker(imageName string) error {
	cmd := exec.Command("docker", "-v")
	if err := cmd.Run(); err != nil {
		return errors.New("请先安装docker或启动docker服务")
	}
	cmd = exec.Command("docker", "image", "inspect", imageName)
	if err := cmd.Run(); err == nil {
		return nil
	}
	cmd = exec.Command("docker", "pull", imageName)
	var stdErr bytes.Buffer
	cmd.Stdout = os.Stdout
	cmd.Stderr = &stdErr
	if err := cmd.Run(); err != nil {
		if strings.Index(stdErr.String(), "./pipe/dockerDesktopLinuxEngine") != -1 {
			return errors.New("请先启动docker服务")
		}
		return errors.New(stdErr.String())
	}
	return nil
}
func runDocker(name, phpDir, uploadDir string, port uint16) error {
	phpDir, _ = filepath.Abs(phpDir)
	uploadDir, _ = filepath.Abs(uploadDir)
	tmpDockerComposeFile := strings.Replace(dockerComposeFile, "{uploadDir}", uploadDir, -1)
	tmpDockerComposeFile = strings.Replace(tmpDockerComposeFile, "{phpDir}", phpDir, -1)
	tmpDockerComposeFile = strings.Replace(tmpDockerComposeFile, "{port}", strconv.Itoa(int(port)), -1)
	tmpDockerComposeFilename := filepath.Join(os.TempDir(), "docker-compose.yml")
	if err := os.WriteFile(tmpDockerComposeFilename, []byte(tmpDockerComposeFile), 0777); err != nil {
		return fmt.Errorf("写 dockerCompose 文件失败 %w", err)
	}
	//defer os.Remove(tmpDockerComposeFilename)
	cmd := exec.Command("docker", "compose", "-f", tmpDockerComposeFilename, "-p", name, "up", "-d")
	var stdErr bytes.Buffer
	cmd.Stderr = &stdErr
	if err := cmd.Run(); err != nil {
		return errors.New(stdErr.String())
	}
	return nil
}
